查看原文
其他

AI人脸识别身份认证系统(3)—训练人脸识别模型

不脱发的程序猿 美男子玩编程 2022-09-10

点击上方蓝色字体,关注我们



1


案例引入



首先简要讲解数据集训练生成模型的原理,这里使用的是LBPH算法,在OpenCV模块中已经有内嵌的方法cv2.face.LBPHFaceRecognizer_create(),为了方便小伙伴们读懂之后的代码,在这里先举一个简单的人脸模型训练的小案例。

第一步:采集人脸数据,网络上有许多案例Demo,不再赘述,代码如下:

import cv2detector = cv2.CascadeClassifier('C:/Users/Administrator/Desktop/haarcascade_frontalface_default.xml')cap = cv2.VideoCapture(0)sampleNum = 0#输入人脸图像数据类别Id = input('enter your id: ')while True: ret, img = cap.read() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = detector.detectMultiScale(gray, 1.3, 5) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) sampleNum = sampleNum + 1 #命名规则为User.[ID].[SampleNumber].jpg #如果是2号人的第十张照片,我们可以将它命名为User.2.10.jpg cv2.imwrite("C:/Users/Administrator/Desktop/dataSet/User." + str(Id) + '.' + str(sampleNum) + ".jpg", gray[y:y + h, x:x + w]) # cv2.imshow('frame', img) if cv2.waitKey(1) & 0xFF == ord('q'): break elif sampleNum > 100: breakcap.release()cv2.destroyAllWindows()

采集效果 如下:


 第二步:使用OpenCV中LBPH算法的方法建立人脸数据模型,代码如下:

import cv2import osimport numpy as npfrom PIL import Image#初始化识别器和人脸检测器'''如果face.LBPHFaceRecognizer_create或createLBPHFaceRecognizer显示不存在则需要下载opencv-contrib-python pip install opencv-contrib-python'''# recognizer = cv2.createLBPHFaceRecognizer()detector = cv2.CascadeClassifier("C:/Users/Administrator/Desktop/haarcascade_frontalface_default.xml")recognizer = cv2.face.LBPHFaceRecognizer_create()'''遍历图片路径,导入图片和id,添加到list'''def get_images_and_labels(path): image_paths = [os.path.join(path, f) for f in os.listdir(path)] face_samples = [] ids = [] for image_path in image_paths: #灰度图片 image = Image.open(image_path).convert('L') #将图片转换成了Numpy数组 image_np = np.array(image, 'uint8') #为了获取到id,我们将图片的路径分裂一下并获取相关信息 if os.path.split(image_path)[-1].split(".")[-1] != 'jpg': continue image_id = int(os.path.split(image_path)[-1].split(".")[1]) faces = detector.detectMultiScale(image_np) #将图片和id都添加在list中 for (x, y, w, h) in faces: face_samples.append(image_np[y:y + h, x:x + w]) ids.append(image_id) return face_samples, ids#让LBPH识别器去训练faces, Ids = get_images_and_labels('C:/Users/Administrator/Desktop/dataSet')recognizer.train(faces, np.array(Ids))recognizer.save('C:/Users/Administrator/Desktop/trainner.yml')

运行程序即可便捷快速生成模型文件“trainner.yml”, 打开模型文件,可以看到人脸数据信息,如下图所示:




2


本节项目



接下来看看本节训练人脸识别模型小案例吧,只不过是在上面代码的基础添加了图像预处理、数据库操作和GUI操作而已,导入第二节采集到的人脸数据,点击训练即可,当人脸数据类别较多时,可以使用数据库进行查询或者删除操作,效果如下:


 确定无误后即可训练模型,效果如下:


训练仅需几秒即可,训练过程中程序会暂停响应,训练成功后就会生成所需模型,大功告成~


 最后分享本节实现代码~

#!/usr/bin/env python3# Author: winterssy <winterssy@foxmail.com>
import cv2import numpy as np
from PyQt5.QtCore import pyqtSignalfrom PyQt5.QtGui import QIcon, QTextCursorfrom PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QTableWidgetItem, QAbstractItemViewfrom PyQt5.uic import loadUi
import loggingimport logging.configimport osimport shutilimport sqlite3import sysimport threadingimport multiprocessing
from datetime import datetime

# 自定义数据库记录不存在异常class RecordNotFound(Exception): pass

class DataManageUI(QWidget): logQueue = multiprocessing.Queue() # 日志队列 receiveLogSignal = pyqtSignal(str) # 日志信号
def __init__(self): super(DataManageUI, self).__init__() loadUi('./ui/DataManage.ui', self) self.setWindowIcon(QIcon('./icons/icon.png')) self.setFixedSize(931, 577)
# 设置tableWidget只读,不允许修改 self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 数据库 self.database = './FaceBase.db' self.datasets = './datasets' self.isDbReady = False self.initDbButton.clicked.connect(self.initDb)
# 用户管理 self.queryUserButton.clicked.connect(self.queryUser) self.deleteUserButton.clicked.connect(self.deleteUser)
# 直方图均衡化 self.isEqualizeHistEnabled = False self.equalizeHistCheckBox.stateChanged.connect( lambda: self.enableEqualizeHist(self.equalizeHistCheckBox))
# 训练人脸数据 self.trainButton.clicked.connect(self.train)
# 系统日志 self.receiveLogSignal.connect(lambda log: self.logOutput(log)) self.logOutputThread = threading.Thread(target=self.receiveLog, daemon=True) self.logOutputThread.start()
# 是否执行直方图均衡化 def enableEqualizeHist(self, equalizeHistCheckBox): if equalizeHistCheckBox.isChecked(): self.isEqualizeHistEnabled = True else: self.isEqualizeHistEnabled = False
# 初始化/刷新数据库 def initDb(self): # 刷新前重置tableWidget while self.tableWidget.rowCount() > 0: self.tableWidget.removeRow(0) try: if not os.path.isfile(self.database): raise FileNotFoundError
conn = sqlite3.connect(self.database) cursor = conn.cursor()
res = cursor.execute('SELECT * FROM users') for row_index, row_data in enumerate(res): self.tableWidget.insertRow(row_index) for col_index, col_data in enumerate(row_data): self.tableWidget.setItem(row_index, col_index, QTableWidgetItem(str(col_data))) cursor.execute('SELECT Count(*) FROM users') result = cursor.fetchone() dbUserCount = result[0] except FileNotFoundError: logging.error('系统找不到数据库文件{}'.format(self.database)) self.isDbReady = False self.initDbButton.setIcon(QIcon('./icons/error.png')) self.logQueue.put('Error:未发现数据库文件,你可能未进行人脸采集') except Exception: logging.error('读取数据库异常,无法完成数据库初始化') self.isDbReady = False self.initDbButton.setIcon(QIcon('./icons/error.png')) self.logQueue.put('Error:读取数据库异常,初始化/刷新数据库失败') else: cursor.close() conn.close()
self.dbUserCountLcdNum.display(dbUserCount) if not self.isDbReady: self.isDbReady = True self.logQueue.put('Success:数据库初始化完成,发现用户数:{}'.format(dbUserCount)) self.initDbButton.setText('刷新数据库') self.initDbButton.setIcon(QIcon('./icons/success.png')) self.trainButton.setToolTip('') self.trainButton.setEnabled(True) self.queryUserButton.setToolTip('') self.queryUserButton.setEnabled(True) else: self.logQueue.put('Success:刷新数据库成功,发现用户数:{}'.format(dbUserCount))
# 查询用户 def queryUser(self): stu_id = self.queryUserLineEdit.text().strip() conn = sqlite3.connect(self.database) cursor = conn.cursor()
try: cursor.execute('SELECT * FROM users WHERE stu_id=?', (stu_id,)) ret = cursor.fetchall() if not ret: raise RecordNotFound face_id = ret[0][1] cn_name = ret[0][2] except RecordNotFound: self.queryUserButton.setIcon(QIcon('./icons/error.png')) self.queryResultLabel.setText('<font color=red>Error:此用户不存在</font>') except Exception as e: logging.error('读取数据库异常,无法查询到{}的用户信息'.format(stu_id)) self.queryResultLabel.clear() self.queryUserButton.setIcon(QIcon('./icons/error.png')) self.logQueue.put('Error:读取数据库异常,查询失败') else: self.queryResultLabel.clear() self.queryUserButton.setIcon(QIcon('./icons/success.png')) self.stuIDLineEdit.setText(stu_id) self.cnNameLineEdit.setText(cn_name) self.faceIDLineEdit.setText(str(face_id)) self.deleteUserButton.setEnabled(True) finally: cursor.close() conn.close()
# 删除用户 def deleteUser(self): text = '从数据库中删除该用户,同时删除相应人脸数据,<font color=red>该操作不可逆!</font>' informativeText = '<b>是否继续?</b>' ret = DataManageUI.callDialog(QMessageBox.Warning, text, informativeText, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if ret == QMessageBox.Yes: stu_id = self.stuIDLineEdit.text() conn = sqlite3.connect(self.database) cursor = conn.cursor()
try: cursor.execute('DELETE FROM users WHERE stu_id=?', (stu_id,)) except Exception as e: cursor.close() logging.error('无法从数据库中删除{}'.format(stu_id)) self.deleteUserButton.setIcon(QIcon('./icons/error.png')) self.logQueue.put('Error:读写数据库异常,删除失败') else: cursor.close() conn.commit() if os.path.exists('{}/stu_{}'.format(self.datasets, stu_id)): try: shutil.rmtree('{}/stu_{}'.format(self.datasets, stu_id)) except Exception as e: logging.error('系统无法删除删除{}/stu_{}'.format(self.datasets, stu_id)) self.logQueue.put('Error:删除人脸数据失败,请手动删除{}/stu_{}目录'.format(self.datasets, stu_id))
text = '你已成功删除学号为 <font color=blue>{}</font> 的用户记录。'.format(stu_id) informativeText = '<b>请在右侧菜单重新训练人脸数据。</b>' DataManageUI.callDialog(QMessageBox.Information, text, informativeText, QMessageBox.Ok)
self.stuIDLineEdit.clear() self.cnNameLineEdit.clear() self.faceIDLineEdit.clear() self.initDb() self.deleteUserButton.setIcon(QIcon('./icons/success.png')) self.deleteUserButton.setEnabled(False) self.queryUserButton.setIcon(QIcon()) finally: conn.close()
# 检测人脸 def detectFace(self, img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if self.isEqualizeHistEnabled: gray = cv2.equalizeHist(gray) face_cascade = cv2.CascadeClassifier('./haarcascades/haarcascade_frontalface_default.xml') faces = face_cascade.detectMultiScale(gray, scaleFactor=1.3, minNeighbors=5, minSize=(90, 90))
if (len(faces) == 0): return None, None (x, y, w, h) = faces[0] return gray[y:y + w, x:x + h], faces[0]
# 准备图片数据 def prepareTrainingData(self, data_folder_path): dirs = os.listdir(data_folder_path) faces = [] labels = []
face_id = 1 conn = sqlite3.connect(self.database) cursor = conn.cursor()
# 遍历人脸库 for dir_name in dirs: if not dir_name.startswith('stu_'): continue stu_id = dir_name.replace('stu_', '') try: cursor.execute('SELECT * FROM users WHERE stu_id=?', (stu_id,)) ret = cursor.fetchall() if not ret: raise RecordNotFound cursor.execute('UPDATE users SET face_id=? WHERE stu_id=?', (face_id, stu_id,)) except RecordNotFound: logging.warning('数据库中找不到学号为{}的用户记录'.format(stu_id)) self.logQueue.put('发现学号为{}的人脸数据,但数据库中找不到相应记录,已忽略'.format(stu_id)) continue subject_dir_path = data_folder_path + '/' + dir_name subject_images_names = os.listdir(subject_dir_path) for image_name in subject_images_names: if image_name.startswith('.'): continue image_path = subject_dir_path + '/' + image_name image = cv2.imread(image_path) face, rect = self.detectFace(image) if face is not None: faces.append(face) labels.append(face_id) face_id = face_id + 1
cursor.close() conn.commit() conn.close()
return faces, labels
# 训练人脸数据 def train(self): try: if not os.path.isdir(self.datasets): raise FileNotFoundError
text = '系统将开始训练人脸数据,界面会暂停响应一段时间,完成后会弹出提示。' informativeText = '<b>训练过程请勿进行其它操作,是否继续?</b>' ret = DataManageUI.callDialog(QMessageBox.Question, text, informativeText, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if ret == QMessageBox.Yes: face_recognizer = cv2.face.LBPHFaceRecognizer_create() if not os.path.exists('./recognizer'): os.makedirs('./recognizer') faces, labels = self.prepareTrainingData(self.datasets) face_recognizer.train(faces, np.array(labels)) face_recognizer.save('./recognizer/trainingData.yml') except FileNotFoundError: logging.error('系统找不到人脸数据目录{}'.format(self.datasets)) self.trainButton.setIcon(QIcon('./icons/error.png')) self.logQueue.put('未发现人脸数据目录{},你可能未进行人脸采集'.format(self.datasets)) except Exception as e: logging.error('遍历人脸库出现异常,训练人脸数据失败') self.trainButton.setIcon(QIcon('./icons/error.png')) self.logQueue.put('Error:遍历人脸库出现异常,训练失败') else: text = '<font color=green><b>Success!</b></font> 系统已生成./recognizer/trainingData.yml' informativeText = '<b>人脸数据训练完成!</b>' DataManageUI.callDialog(QMessageBox.Information, text, informativeText, QMessageBox.Ok) self.trainButton.setIcon(QIcon('./icons/success.png')) self.logQueue.put('Success:人脸数据训练完成') self.initDb()
# 系统日志服务常驻,接收并处理系统日志 def receiveLog(self): while True: data = self.logQueue.get() if data: self.receiveLogSignal.emit(data) else: continue
# LOG输出 def logOutput(self, log): time = datetime.now().strftime('[%Y/%m/%d %H:%M:%S]') log = time + ' ' + log + '\n'
self.logTextEdit.moveCursor(QTextCursor.End) self.logTextEdit.insertPlainText(log) self.logTextEdit.ensureCursorVisible() # 自动滚屏
# 系统对话框 @staticmethod def callDialog(icon, text, informativeText, standardButtons, defaultButton=None): msg = QMessageBox() msg.setWindowIcon(QIcon('./icons/icon.png')) msg.setWindowTitle('OpenCV Face Recognition System - DataManage') msg.setIcon(icon) msg.setText(text) msg.setInformativeText(informativeText) msg.setStandardButtons(standardButtons) if defaultButton: msg.setDefaultButton(defaultButton) return msg.exec()

if __name__ == '__main__': logging.config.fileConfig('./config/logging.cfg') app = QApplication(sys.argv) window = DataManageUI() window.show() sys.exit(app.exec())


往期推荐



点击阅读原文,更精彩~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存